import os, sys, Queue, subprocess, random
import threading
import time
from threading import *
from subprocess import PIPE

from ivic.core import Logging
logger = Logging.get_logger('ivic.webfarm.WorkNode') 

class WorkNodeInfo(object):
    """
    information of worknode in webhosting environment.
    rip: real internal ip
    vip:virtual external ip
    """
    
    def __init__(self, dict=None):
        self.dict = {'vip':{'ipv4':''}, 'rip':{'ipv4':''},
                     'conn':[], 'weight':0.0, 'neg_conn':[], 'arrival':0.0 }
        try:
            for key, value in dict.items():
                if self.dict.has_key(key): self.dict[key] = value
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            pass
            
    def __getitem__(self, attr):
        try:
            if self.dict.has_key(attr): return self.dict[attr]
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
        
    def __setitem__(self, attr, value):
        try:
            if self.dict.has_key(attr): self.dict[attr] = value
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise 
        
    def update(self, info_dict):
        try:
            logger.debug(info_dict)
            
            self.dict['conn'].append(info_dict['conn'])
            self.dict['weight'] = info_dict['weight']
            self.dict['neg_conn'].append(info_dict['neg_conn'])
            
            logger.debug('update %s finished.' % self.dict['rip'])
        except Exception , ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
    
    def clear(self):
        try:
            self.dict['conn'] = []
            self.dict['neg_conn'] = []
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
        
    def __str__(self):
        return str(self.dict)
    
    def __repr__(self):
        return str(self.dict)
        
class WorkNodeMgr(object):
    
    def __init__(self, vm_uuid, vip, max_limited_conn=500):
        
        #basic info
        self.vm_uuid = vm_uuid
        self.vip = vip
        self.max_limited_conn = max_limited_conn
        
        #nodes info
        self.nodes = {}    
        self.lock = RLock()
        self.data = Queue.Queue()
        self.load = []
        
        #init ipvsadm
        self.init_ipvsadm()
        
    def init_ipvsadm(self):
        try:
            command = """ifconfig eth0:0 %s broadcast %s netmask 255.255.255.255 up;
                         ifconfig eth0:0;
                         route add -host %s dev eth0:0;
                         route;
                         echo "1" >/proc/sys/net/ipv4/ip_forward;
                         ipvsadm -C;
                         ipvsadm -A -t %s:80 -s rr"""
            cmd = command % (self.vip, self.vip[:-3] + "255", self.vip, self.vip)
            proc = subprocess.Popen(cmd, close_fds=True, shell=True)
            proc.wait()
            nodesfile = open('/etc/ivic/nodes.yaml', 'r')
            nodesNum = nodesfile.readline()
            if int(nodesNum) >= 1:
                for i in range(1, int(nodesNum) + 1):
                    self.add_node(WorkNodeInfo({'vip':{'ipv4':self.vip}, 'rip':{'ipv4':self.vip[:-3] + str(100 + i)}}))
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
        
    def add_node(self, node):
        try:
            if type(node) is WorkNodeInfo:
                # maybe we can add lock here?
                command = "ipvsadm -a -t %s:80 -r %s:80 -m"
                cmd = command % (node['vip']['ipv4'], node['rip']['ipv4'])
                proc = subprocess.Popen(cmd, close_fds=True, shell=True)
                retcode = proc.wait()
                if retcode >= 0 and not self.nodes.has_key(node['rip']['ipv4']):
                    self.nodes[node['rip']['ipv4']] = node
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
    
    def remove_node(self, ip):
        try:
            if self.nodes.has_key(ip):
                sip = self.nodes[ip]['vip']['ipv4']
                cmd = "ipvsadm -d -t %s:80 -r %s:80" % (sip, ip)
                proc = subprocess.Popen(cmd, close_fds=True, shell=True)
                retcode = proc.wait()
                if retcode >= 0:
                    node = self.nodes[ip]
                    self.nodes.pop(ip)
                    logger.debug('delete node : %s' % node)
                return node
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
    
    def add_nodes(self, nodes):
        success = []
        try:
            if type(nodes) is list:
                for node in nodes:
                    try:
                        self.add_node(node)
                        success.append(node)
                    except Exception, ex:
                        logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
                        pass
            else:
                raise Exception('argument error: "nodes" is not list.')
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
        return success
    
    def batch_add_ips(self, vip, ips):
        try:
            nodes = []
            for ip in ips:
                nodes.append(WorkNodeInfo({'vip':{'ipv4':vip}, 'rip':{'ipv4':ip}}))
            if len(nodes): return len(self.add_nodes(nodes)) == len(nodes)
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            return False
            
    def remove_nodes(self, ips):
        removed = []
        try:
            if type(ips) is list:
                for ip in ips:
                    try:
                        node = self.remove_node(ip)
                        if node: removed.append(ip)
                    except Exception, ex:
                        logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            else:
                raise Exception('argument error: "ips" is not list.')
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
        return removed
    
    def remove_top_k(self, k=0, attr='conn', asc=True):
        try:
            ds = self.top_k_by(k, attr, asc)
            if type(ds) is dict: return self.remove_nodes(ds.keys())
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            return []
            
    def top_k_by(self, k=0, attr='conn', asc=True):
        try:
            #self.nodes.values().sort(cmp=compare)
            cand = [node for node in self.nodes.values()]
            
            for each in cand:
                logger.debug('item in cand: %s' % each)
            
            #should be fixed the sorting method
            try:
                #what about string?
                compare = lambda x, y: x[attr] - y[attr]
                #connection attribute is a conns list
                if attr == 'conn':
                    rd = lambda cons: sum(cons) / (len(cons) + 1)
                    compare = lambda x, y: rd(x[attr]) - rd(y[attr])
                    
                cand.sort(cmp=compare)
            except Exception, ex:
                logger.error('sort candinate exception: %s' % ex)    
                
            ds = {}
            try:
                for each in cand:
                    if k <= 0: break
                    ds[each['rip']['ipv4']] = each
                    k -= 1
            except Exception, ex:
                logger.error('%s' % ex)
            
            logger.debug('nodes to be removed: %s' % ds)
            return ds    
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
    
    def update_data(self, info_dict):
        try:
            if not type(info_dict) is dict: return False
            
            if len(info_dict) <= 0:
                self.load.append(1.0)
                return True
            
            self.nodes.clear()
            for ip, data in info_dict.items():
                try:
                    node = WorkNodeInfo()
                    node['rip']['ipv4'] = ip
                    node['vip']['ipv4'] = self.vip
                    node.update(data)
                    self.nodes[ip] = node
                except Exception, e:
                    logger.error('%s' % e)
                    
            #calculate load and put in the list
            load = 0.0
            # check real load
            command = 'ipvsadm -ln --rate'
            proc = subprocess.Popen(command, close_fds=True, shell=True, stdout=PIPE)
            proc.wait()
            output = proc.communicate()[0].split('\n')
            if len(output) > 3:
                # parse ipvs node
                ipvsNode = output[3].split()
                ipvsCPS = ipvsNode[2]
                load = float(ipvsCPS) / float((len(info_dict) * self.max_limited_conn))
            self.load.append(load)    
            return True
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
        
        
    def clear_data(self):
        try:
            self.load = []
            for ip, node in self.nodes.items():
                try:
                    node.clear()
                except Exception, ex:
                    logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
                    pass
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
        
    def dump_data(self):
        try:
            #copy data
            data = self.nodes.copy()
            load = self.load
            
            #clear data
            self.clear_data()
            
            #formating message
            logger.debug(load)
            load_format = lambda s, item: s + '%f ' % item
            cur_load = 'current_load=%s' % reduce(load_format, load, "")
            
            active = lambda node: node['weight'] > 0
            cand = filter(active, data.values())
            format = lambda acc, node: acc + "%s " % node['rip']['ipv4']
            online = "online=%s" % reduce(format, cand, "")
            current_node = "current_node=%d" % len(cand)
            
            uuid = "vm_uuid=%s" % self.vm_uuid
            
            msg_format = "%s&%s&%s&%s"
            message = msg_format % (uuid, cur_load, current_node, online)
            
            #put the message to queue, waiting to be poped out
            logger.debug('message: %s' % message)
            self.data.put(message)
            #return message
        except Exception, ex:
            logger.error("%s:%s" % (sys._getframe().f_code.co_name, ex))
            raise
